package org.ithasu.scheduler;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;

import org.ithasu.tools.CollectionTools;

public class Scheduler {

	private final Vector<SchedulerActivity> activities = new Vector<SchedulerActivity>();
	private Thread loopThread = null;

	public Scheduler() {
	}

	// -- Activities ----------------------------------------

	/**
	 * Stacks a one time runnable
	 */
	public void stack(final Runnable runnable) {
		if (loopThread == null) {
			runnable.run();
		} else {
			addActivity(new SchedulerRunnable() {
				@Override
				public long run(Scheduler scheduler, long dt) {
					runnable.run();
					return -1;
				}
			});
			loopThread.interrupt();
		}
	}

	/**
	 * Will add activity and run it now
	 */
	public void addActivity(SchedulerRunnable runnable) {
		addActivity(runnable, -1);
	}

	/**
	 * Will add activity and run it after dt
	 */
	public void addActivity(SchedulerRunnable runnable, long dt) {
		activities.add(new SchedulerActivity(null,
				dt <= 0 ? 0 : getTime() + dt, runnable));
	}

	// -- Time ----------------------------------------

	public long getTime() {
		return System.currentTimeMillis();
	}

	// -- Time ----------------------------------------

	public void start() {
		if (loopThread != null) {
			return;
		}

		loopThread = new Thread(new Runnable() {
			@Override
			public void run() {
				loop();
			}
		});
	}

	public void loop() {
		while (true) {
			step();
		}
	}

	public void step() {
		stepImpl();
	}

	protected final void stepImpl() {
		long time = getTime();

		// List things to launch
		ArrayList<SchedulerActivity> toLaunch = new ArrayList<SchedulerActivity>();
		Long nextLaunch = null;
		synchronized (activities) {
			Iterator<SchedulerActivity> it = activities.iterator();
			while (it.hasNext()) {
				SchedulerActivity e = it.next();
				long n = e.nextRun;
				if (n <= 0 || n <= time) {
					// need to be launched
					toLaunch.add(e);
					it.remove();
				}
				if (nextLaunch == null || n < nextLaunch) {
					nextLaunch = n;
				}
			}
		}

		if (CollectionTools.isEmpty(toLaunch)) {
			// Wait
			try {
				long dt = nextLaunch - time;
				if (dt > 0) {
					Thread.sleep(dt);
				}
			} catch (InterruptedException e) {
				// interrupted by an event manager
				// don't care, handle events
			}
		} else {
			// Run activities
			ArrayList<SchedulerActivity> nextToLaunch = new ArrayList<SchedulerActivity>();
			for (SchedulerActivity act : toLaunch) {
				long nextDt = act.runnable.run(this, act.getDT());
				if (nextDt > 0) {
					long t = getTime();
					nextToLaunch.add(new SchedulerActivity(t, t + nextDt,
							act.runnable));
				}
			}

			synchronized (activities) {
				activities.addAll(nextToLaunch);
			}
		}
	}

	// -- Activities ----------------------------------------

	private static class SchedulerActivity {
		public final Long lastRun;
		public final long nextRun;
		public final SchedulerRunnable runnable;

		public SchedulerActivity(Long lastRun, long nextRun,
				SchedulerRunnable runnable) {
			this.lastRun = lastRun;
			this.nextRun = nextRun;
			this.runnable = runnable;
		}

		public long getDT() {
			return lastRun == null ? nextRun : nextRun - lastRun;
		}
	}

	public static interface SchedulerRunnable {
		public long run(Scheduler scheduler, long dt);
	}
}
